package TrapezoidalRule;

import java.util.*;

/**
 *
 * Exercise 1.1
 * @author Katrin Baryshnikova
 */
public class PostfixNotation {

    /**
     * Конструктор
     */
    PostfixNotation() {
        // создаем ответ в памяти
        output = new ArrayList<String>();
    }

    /**
     * Преобразует входное выражение из инфиксной формы в постфиксную и 
     * производит вычисление
     * @param s входная строка
     * @return результат вычислений
     */
    public String calculate(String[] s, String x) {
        convertInPostfix(s, x);     //преобразуем входную последовательность токенов постфиксную запись
        if (output.get(0) == "ERROR") { // если первый элемент последовательности "ERROR"
            return "ERROR";             // возвращаем сообщение об ошибке
        }
        Double calcAnswer = 0.0;    // ответ
        // временные переменные для вычислений
        Double a = 0.0; 
        Double b = 0.0;
        // стек 
        Stack<String> st = new Stack<String>();
        // блок try/catch для перехвата ошибки в случае неверно заданнго выражения
        try {
            // если в постфиксной записи более 1 элемента производим вычисления
            if (output.size() > 1) { 
                for (int i = 0; i < output.size(); i++) {
                    String token = output.get(i); // получаем i-ый токен
                    if (isNumeric(token)) { // если он является числом
                        st.push(token); // помещаем его в стек
                    } else if (isOperation(token)) { // если он является знаком операции
                        // получаем два числа с вершины стека
                        if (!st.empty()) { 
                            a = Double.parseDouble(st.pop());
                        }
                        if (!st.empty()) {
                            b = Double.parseDouble(st.pop());
                        }
                        // и вычисляем 
                        calcAnswer = evaluate(a, b, token);
                        // результат кладем в стек
                        st.push(calcAnswer.toString());
                    }
                }
            } else {
                // иначе в ответ помещаем x
                calcAnswer = Double.parseDouble(x);
            }
        } catch (Exception e) { // ловим ошибку
            return "ERROR"; 
        }        
        return calcAnswer.toString();
    }

    /**
     * Преобразует входную последовательность из инфиксной формы в постфиксную
     * @param s  входная последовательность
     */
    private void convertInPostfix(String[] s, String x) {
        int n = s.length; // длина последовательности 
        output = new ArrayList<String>(); 
        // создаем стек
        Stack<String> st = new Stack<String>(); 
        String symb = null; // временная переменная
        // блок try/catch отлавливает сообщения об ошибках
        try { 
            for (int i = 0; i < n; i++) {
                symb = s[i];    // получаем i-ый элемент входной строки 
                if (isNumeric(symb)) { // если он - число 
                    output.add(symb); // добавляем его в выходную последовательность
                } else if (symb.equals("x")) {  // если он - x
                    output.add(x);  // добавляем в выходную строку значение х
                } else if (isOpenScope(symb)) {  // если открывающаяся скобка 
                    st.push(symb);  // помещаем ее в стек
                } else if (isCloseScope(symb)) { // если закрывающая скобка 
                    // пока стек пуст или пока мы не встретим открывающую скобку
                    while (!st.isEmpty() && !isOpenScope(st.peek())) {
                        output.add(st.pop());   //  добавляем в выходную токены с вершины стека последовательность
                    }
                    st.pop(); // выкидываем из стека открывающую скобку
                } else if (isOperation(symb)) { // если операция
                    // пока стек не пуст и пока приоритет данной операции не больше приоритета операции на вершине стека
                    while (!st.isEmpty() && getPriority(symb) <= getPriority(st.peek())) {
                        // добавляем операции в выходную последовательность
                        output.add(st.pop());
                    }
                    // помещаем текущую операцию на вершину стека
                    st.push(symb);
                }
            }
            // добавляем в выходную последовательность все элементы оставшиеся в стеке
            while (!st.empty()) {
                output.add(st.pop());
            }
        } catch (EmptyStackException e) { // если стек пустой, значит введена неправильная 
                                            //скобочная запись и ответ заполняется сообщением об ошибке
            output.clear(); 
            output.add("ERROR");
        }
    }

    /**
     * Определяет является ли входная строка числом
     * @param s входная строка
     * @return true если входная строка является числом
     */
    private static boolean isNumeric(String s) {
        try {
            Double.parseDouble(s);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * Определяет является ли входная строка открывающей скобкой
     * @param s входная строка
     * @return true если входная строка является '('
     */
    private static boolean isOpenScope(String s) {
        return s.equals("(") ? true : false;
    }

    /**
     * Определяет является ли входная строка закрывающей скобкой
     * @param s входная строка
     * @return true если входная строка является ')'
     */
    private static boolean isCloseScope(String s) {
        return s.equals(")") ? true : false;
    }

    /**
     * Определяет является ли входная строка арифметической операцией
     * @param s входная строка
     * @return true если входная строка является +, -, /, * 
     */
    private static boolean isOperation(String s) {
        return s.equals("*") || s.equals("/") || s.equals("+")
                || s.equals("-") || s.equals("^") ? true : false;
    }

    /**
     * Вычисляет значение выражения с двумя переменными 
     * @param a первая переменная
     * @param b вторая переменная
     * @param oper - оператор
     * @return 
     */
    private static Double evaluate(Double a, Double b, String oper) {
        if (oper.equals("^")) {
            return Math.pow(b.doubleValue(), a.doubleValue());
        } else if (oper.equals("/")) {
            return b / a;
        } else if (oper.equals("*")) {
            return b * a;
        } else if (oper.equals("+")) {
            return b + a;
        } else if (oper.equals("-")) {
            return b - a;
        } else {
            return 0.0;
        }
    }
    /**
     * Возвращает приоритет операций
     * @param s входная строка
     * @return 
     */
    private static int getPriority(String s) {
        if (s.equals("^")) {
            return 3;
        }
        if (s.equals("/") || s.equals("*")) {
            return 2;
        } else if (s.equals("+") || s.equals("-")) {
            return 1;
        } else {
            return 0;
        }
    }
    private ArrayList<String> output;
}
